import type { EncryptionParams } from '../';
import { AesEncryption, isNullOrUndefined } from '../';

export interface CreateStorageParams extends EncryptionParams {
  /** 前缀 */
  prefixKey: string;
  /** local or session */
  storage: Storage;
  /** 是否加密 */
  hasEncrypt: boolean;
  /** 默认过期时间 */
  timeout?: Nullable<number>;
}

/**
 * @description 创建本地存储
 */
export const createStorage = ({ prefixKey = '', storage = localStorage, key, iv, timeout = null, hasEncrypt = false }: Partial<CreateStorageParams> = {}) => {
  if (hasEncrypt && [key?.length, iv?.length].some((item) => item !== 16)) {
    throw new Error('When hasEncrypt is true, the key or iv must be 16 bits!');
  }
  const encryption = new AesEncryption({ key, iv });

  const WebStorage = class WebStorage {
    private storage: Storage;
    private prefixKey?: string;
    private encryption: AesEncryption;
    private hasEncrypt: boolean;

    constructor() {
      this.storage = storage;
      this.prefixKey = prefixKey;
      this.encryption = encryption;
      this.hasEncrypt = hasEncrypt;
    }

    private getKey(key: string) {
      return `${this.prefixKey}${key}`.toUpperCase();
    }

    set(key: string, value: any, expire: number | null = timeout) {
      const stringData = JSON.stringify({
        value,
        time: Date.now(),
        expire: !isNullOrUndefined(expire) ? new Date().getTime() + expire * 1000 : null
      });
      /** 数据是否加密 */
      const stringifyValue = this.hasEncrypt ? this.encryption.encryptByAES(stringData) : stringData;
      this.storage.setItem(this.getKey(key), stringifyValue);
    }

    /**
     * @description 读取缓存
     */
    get(key: string, def: any = null): any {
      const val = this.storage.getItem(this.getKey(key));
      if (!val) return def;

      try {
        const decVal = this.hasEncrypt ? this.encryption.decryptByAES(val) : val;
        const data = JSON.parse(decVal);
        const { value, expire } = data;
        if (isNullOrUndefined(expire) || expire >= new Date().getTime()) {
          return value;
        }
        this.remove(key);
      } catch (e) {
        return def;
      }
    }

    remove(key: string) {
      this.storage.removeItem(this.getKey(key));
    }

    clear(): void {
      this.storage.clear();
    }
  };
  return new WebStorage();
};

export const createSessionStorage = (options: Partial<CreateStorageParams> = {}) => {
  return createStorage({
    storage: sessionStorage,
    ...options
  });
};
